
/*******************************************************************
 *
 *  ttload.c                                                    1.0
 *
 *    TrueType Tables Loader.
 *
 *  Copyright 1996-1999 by
 *  David Turner, Robert Wilhelm, and Werner Lemberg.
 *
 *  This file is part of the FreeType project, and may only be used
 *  modified and distributed under the terms of the FreeType project
 *  license, LICENSE.TXT.  By continuing to use, modify, or distribute
 *  this file you indicate that you have read the license and
 *  understand and accept it fully.
 *
 ******************************************************************/

#include "tttypes.h"
#include "ttdebug.h"
#include "ttcalc.h"
#include "ttfile.h"

#include "tttables.h"
#include "ttobjs.h"

#include "ttmemory.h"
#include "tttags.h"
#include "ttload.h"

/* required by the tracing mode */
#undef  TT_COMPONENT
#define TT_COMPONENT      trace_load

/* In all functions, the stream is taken from the 'face' object */
#define DEFINE_LOCALS           DEFINE_LOAD_LOCALS( face->stream )
#define DEFINE_LOCALS_WO_FRAME  DEFINE_LOAD_LOCALS_WO_FRAME( face->stream )

/*******************************************************************
 *
 *  Function    :  LookUp_TrueType_Table
 *
 *  Description :  Looks for a TrueType table by name.
 *
 *  Input  :  face       face table to look for
 *            tag        searched tag
 *
 *  Output :  Index of table if found, -1 otherwise.
 *
 ******************************************************************/

EXPORT_FUNC Long TT_LookUp_Table(PFace face, ULong tag)
{
	UShort i;

	PTRACE4(("TT_LookUp_Table( %08lx, %c%c%c%c )\n", (Long) face, (Char) (tag >> 24), (Char) (tag >> 16),
			 (Char) (tag >> 8), (Char) (tag)));

	for (i = 0; i < face->numTables; i++)
		if (face->dirTables[i].Tag == tag)
			return i;

	PTRACE4(("    Could not find table!\n"));
	return -1;
}

/*******************************************************************
 *
 *  Function    :  Load_TrueType_Collection
 *
 *  Description :  Loads the TTC table directory into face table.
 *
 *  Input  :  face    face record to look for
 *
 *  Output :  Error code.
 *
 ******************************************************************/

static TT_Error Load_TrueType_Collection(PFace face)
{
	DEFINE_LOCALS;

	ULong n;

	PTRACE3(("Load_TrueType_Collection( %08lx )\n", (long) face));

	if (FILE_Seek(0L) || ACCESS_Frame(12L))
		return error;

	face->ttcHeader.Tag = GET_Tag4();
	face->ttcHeader.version = GET_Long();
	face->ttcHeader.DirCount = GET_Long();

	FORGET_Frame();

	if (face->ttcHeader.Tag != TTAG_ttcf) {
		face->ttcHeader.Tag = 0;
		face->ttcHeader.version = 0;
		face->ttcHeader.DirCount = 0;

		face->ttcHeader.TableDirectory = NULL;

		PTRACE3(("skipped.\n"));

		return TT_Err_File_Is_Not_Collection;
	}

	if (ALLOC_ARRAY(face->ttcHeader.TableDirectory, face->ttcHeader.DirCount, ULong)
		|| ACCESS_Frame(face->ttcHeader.DirCount * 4L))
		return error;

	for (n = 0; n < face->ttcHeader.DirCount; n++)
		face->ttcHeader.TableDirectory[n] = GET_ULong();

	FORGET_Frame();

	PTRACE3(("collections directory loaded.\n"));
	return TT_Err_Ok;
}

/*******************************************************************
 *
 *  Function    :  Load_TrueType_Directory
 *
 *  Description :  Loads the table directory into face table.
 *
 *  Input  :  face       face record to look for
 *
 *            faceIndex  the index of the TrueType font, when
 *                       we're opening a collection.
 *
 *  Output :  SUCCESS on success.  FAILURE on error.
 *
 ******************************************************************/

LOCAL_FUNC TT_Error Load_TrueType_Directory(PFace face, ULong faceIndex)
{
	DEFINE_LOCALS;

	UShort n, limit;
	TTableDir tableDir;

	PTableDirEntry entry;

	PTRACE2(("Load_TT_Directory( %08lx, %ld )\n", (long) face, faceIndex));

	error = Load_TrueType_Collection(face);

	if (error) {
		if (error != TT_Err_File_Is_Not_Collection)
			return error;

		/* the file isn't a collection, exit if we're asking */
		/* for a collected font                              */
		if (faceIndex != 0)
			return error;

		/* Now skip to the beginning of the file */
		if (FILE_Seek(0L))
			return error;
	} else {
		/* The file is a collection. Check the font index */
		if (faceIndex >= face->ttcHeader.DirCount)
			return TT_Err_Invalid_Argument;

		/* select a TrueType font in the ttc file   */
		if (FILE_Seek(face->ttcHeader.TableDirectory[faceIndex]))
			return error;
	}

	if (ACCESS_Frame(12L))
		return error;

	tableDir.version = GET_Long();
	tableDir.numTables = GET_UShort();

	tableDir.searchRange = GET_UShort();
	tableDir.entrySelector = GET_UShort();
	tableDir.rangeShift = GET_UShort();

	FORGET_Frame();

	PTRACE2(("-- Tables count   : %12u\n", tableDir.numTables));
	PTRACE2(("-- Format version : %08lx\n", tableDir.version));

	/* Check that we have a 'sfnt' format there */

	if (tableDir.version != 0x00010000 &&	/* MS fonts */
		tableDir.version != 0x74727565 &&	/* Mac fonts */
		tableDir.version != 0x00000000) {	/* some Korean fonts */
		PERROR(("!! invalid file format"));
		return TT_Err_Invalid_File_Format;
	}

	face->numTables = tableDir.numTables;

	if (ALLOC_ARRAY(face->dirTables, face->numTables, TTableDirEntry))
		return error;

	if (ACCESS_Frame(face->numTables * 16L))
		return error;

	limit = face->numTables;
	entry = face->dirTables;

	for (n = 0; n < limit; n++) {	/* loop through the tables and get all entries */
		entry->Tag = GET_Tag4();
		entry->CheckSum = GET_ULong();
		entry->Offset = GET_Long();
		entry->Length = GET_Long();

		PTRACE2(("  %c%c%c%c  -  %08lx  -  %08lx\n", (Char) (entry->Tag >> 24), (Char) (entry->Tag >> 16),
				 (Char) (entry->Tag >> 8), (Char) (entry->Tag), entry->Offset, entry->Length));
		entry++;
	}

	FORGET_Frame();

	PTRACE2(("Directory loaded\n\n"));
	return TT_Err_Ok;
}

/*******************************************************************
 *
 *  Function    :  Load_TrueType_MaxProfile
 *
 *  Description :  Loads the maxp table into face table.
 *
 *  Input  :  face     face table to look for
 *
 *  Output :  Error code.
 *
 ******************************************************************/

LOCAL_FUNC TT_Error Load_TrueType_MaxProfile(PFace face)
{
	DEFINE_LOCALS;

	Long i;
	PMaxProfile maxProfile = &face->maxProfile;

	PTRACE2(("Load_TT_MaxProfile( %08lx )\n", (long) face));

	if ((i = TT_LookUp_Table(face, TTAG_maxp)) < 0)
		return TT_Err_Max_Profile_Missing;

	if (FILE_Seek(face->dirTables[i].Offset))	/* seek to maxprofile */
		return error;

	if (ACCESS_Frame(32L))	/* read into frame */
		return error;

	/* read frame data into face table */
	maxProfile->version = GET_ULong();

	maxProfile->numGlyphs = GET_UShort();

	maxProfile->maxPoints = GET_UShort();
	maxProfile->maxContours = GET_UShort();
	maxProfile->maxCompositePoints = GET_UShort();
	maxProfile->maxCompositeContours = GET_UShort();

	maxProfile->maxZones = GET_UShort();
	maxProfile->maxTwilightPoints = GET_UShort();

	maxProfile->maxStorage = GET_UShort();
	maxProfile->maxFunctionDefs = GET_UShort();
	maxProfile->maxInstructionDefs = GET_UShort();
	maxProfile->maxStackElements = GET_UShort();
	maxProfile->maxSizeOfInstructions = GET_UShort();
	maxProfile->maxComponentElements = GET_UShort();
	maxProfile->maxComponentDepth = GET_UShort();

	FORGET_Frame();

	/* XXX : an adjustement that is necessary to load certain */
	/*       broken fonts like "Keystrokes MT" :-(            */
	/*                                                        */
	/*   We allocate 64 function entries by default when      */
	/*   the maxFunctionDefs field is null.                   */

	if (maxProfile->maxFunctionDefs == 0)
		maxProfile->maxFunctionDefs = 64;

	face->numGlyphs = maxProfile->numGlyphs;

	face->maxPoints = MAX(maxProfile->maxCompositePoints, maxProfile->maxPoints);
	face->maxContours = MAX(maxProfile->maxCompositeContours, maxProfile->maxContours);
	face->maxComponents = maxProfile->maxComponentElements + maxProfile->maxComponentDepth;

	/* XXX: Some fonts have maxComponents set to 0; we will */
	/*      then use 16 of them by default.                 */
	if (face->maxComponents == 0)
		face->maxComponents = 16;

	/* We also increase maxPoints and maxContours in order to support */
	/* some broken fonts.                                             */
	face->maxPoints += 8;
	face->maxContours += 4;

	PTRACE2(("GASP loaded.\n"));
	return TT_Err_Ok;
}

/*******************************************************************
 *
 *  Function    :  Load_TrueType_Gasp
 *
 *  Description :  Loads the TrueType Gasp table into the face
 *                 table.
 *
 *  Input  :  face     face table to look for
 *
 *  Output :  Error code.
 *
 ******************************************************************/

LOCAL_FUNC TT_Error Load_TrueType_Gasp(PFace face)
{
	DEFINE_LOCALS;

	Long i;
	UShort j;
	TGasp *gas;
	GaspRange *gaspranges;

	PTRACE2(("Load_TT_Gasp( %08lx )\n", (long) face));

	if ((i = TT_LookUp_Table(face, TTAG_gasp)) < 0)
		return TT_Err_Ok;	/* gasp table is not required */

	if (FILE_Seek(face->dirTables[i].Offset) || ACCESS_Frame(4L))
		return error;

	gas = &face->gasp;

	gas->version = GET_UShort();
	gas->numRanges = GET_UShort();

	FORGET_Frame();

	PTRACE3(("number of ranges = %d\n", gas->numRanges));

	if (ALLOC_ARRAY(gaspranges, gas->numRanges, GaspRange) || ACCESS_Frame(gas->numRanges * 4L))
		goto Fail;

	face->gasp.gaspRanges = gaspranges;

	for (j = 0; j < gas->numRanges; j++) {
		gaspranges[j].maxPPEM = GET_UShort();
		gaspranges[j].gaspFlag = GET_UShort();

		PTRACE3((" [max:%d flag:%d]", gaspranges[j].maxPPEM, gaspranges[j].gaspFlag));
	}
	PTRACE3(("\n"));

	FORGET_Frame();

	PTRACE2(("GASP loaded\n"));
	return TT_Err_Ok;

  Fail:
	FREE(gaspranges);
	gas->numRanges = 0;
	return error;
}

/*******************************************************************
 *
 *  Function    :  Load_TrueType_Header
 *
 *  Description :  Loads the TrueType header table into the face
 *                 table.
 *
 *  Input  :  face     face table to look for
 *
 *  Output :  Error code.
 *
 ******************************************************************/

LOCAL_FUNC TT_Error Load_TrueType_Header(PFace face)
{
	DEFINE_LOCALS;

	Long i;
	TT_Header *header;

	PTRACE2(("Load_TT_Header( %08lx )\n", (long) face));

	if ((i = TT_LookUp_Table(face, TTAG_head)) < 0) {
		PTRACE0(("Font Header is missing !!\n"));
		return TT_Err_Header_Table_Missing;
	}

	if (FILE_Seek(face->dirTables[i].Offset) || ACCESS_Frame(54L))
		return error;

	header = &face->fontHeader;

	header->Table_Version = GET_ULong();
	header->Font_Revision = GET_ULong();

	header->CheckSum_Adjust = GET_Long();
	header->Magic_Number = GET_Long();

	header->Flags = GET_UShort();
	header->Units_Per_EM = GET_UShort();

	header->Created[0] = GET_Long();
	header->Created[1] = GET_Long();
	header->Modified[0] = GET_Long();
	header->Modified[1] = GET_Long();

	header->xMin = GET_Short();
	header->yMin = GET_Short();
	header->xMax = GET_Short();
	header->yMax = GET_Short();

	header->Mac_Style = GET_UShort();
	header->Lowest_Rec_PPEM = GET_UShort();

	header->Font_Direction = GET_Short();
	header->Index_To_Loc_Format = GET_Short();
	header->Glyph_Data_Format = GET_Short();

	FORGET_Frame();

	PTRACE2(("    Units per EM : %8u\n", header->Units_Per_EM));
	PTRACE2(("    IndexToLoc   : %8d\n", header->Index_To_Loc_Format));
	PTRACE2(("Font Header Loaded.\n"));
	return TT_Err_Ok;
}

/*******************************************************************
 *
 *  Function    :  Load_TrueType_Metrics
 *
 *  Description :  Loads the horizontal or vertical metrics table
 *                 into face object.
 *
 *  Input  :  face
 *            vertical   set to true when loading the vmtx table,
 *                       or false for hmtx
 *
 *  Output :  Error code.
 *
 ******************************************************************/

static TT_Error Load_TrueType_Metrics(PFace face, Bool vertical)
{
	DEFINE_LOCALS;

	Long n, num_shorts, num_shorts_checked, num_longs;

	PLongMetrics *longs;
	PShortMetrics *shorts;

	PLongMetrics long_metric;

	PTRACE2(("Load_TT_%s_Metrics( %08lx )\n", vertical ? "Vertical" : "Horizontal", (long) face));

	if (vertical) {
		/* The table is optional, quit silently if it wasn't found       */
		/* XXX : Some fonts have a valid vertical header with a non-null */
		/*       "number_of_VMetrics" fields, but no corresponding       */
		/*       'vmtx' table to get the metrics from (e.g. mingliu)     */
		/*                                                               */
		/*       For safety, we set the field to 0 !                     */
		/*                                                               */
		n = TT_LookUp_Table(face, TTAG_vmtx);
		if (n < 0) {
			/* Set the number_Of_VMetrics to 0! */
			PTRACE2(("  no vertical header in file.\n"));
			face->verticalHeader.number_Of_VMetrics = 0;
			return TT_Err_Ok;
		}

		num_longs = face->verticalHeader.number_Of_VMetrics;
		longs = (PLongMetrics *) & face->verticalHeader.long_metrics;
		shorts = (PShortMetrics *) & face->verticalHeader.short_metrics;
	} else {
		if ((n = TT_LookUp_Table(face, TTAG_hmtx)) < 0) {
			PERROR(("!! No Horizontal metrics in file !!\n"));
			return TT_Err_Hmtx_Table_Missing;
		}

		num_longs = face->horizontalHeader.number_Of_HMetrics;
		longs = (PLongMetrics *) & face->horizontalHeader.long_metrics;
		shorts = (PShortMetrics *) & face->horizontalHeader.short_metrics;
	}

	/* never trust derived values! */

	num_shorts = face->maxProfile.numGlyphs - num_longs;
	num_shorts_checked = (face->dirTables[n].Length - num_longs * 4) / 2;

	if (num_shorts < 0) {	/* sanity check */
		PERROR(("!! more metrics than glyphs!\n"));
		if (vertical)
			return TT_Err_Invalid_Vert_Metrics;
		else
			return TT_Err_Invalid_Horiz_Metrics;
	}

	if (ALLOC_ARRAY(*longs, num_longs, TLongMetrics) || ALLOC_ARRAY(*shorts, num_shorts, TShortMetrics))
		return error;

	if (FILE_Seek(face->dirTables[n].Offset) || ACCESS_Frame(face->dirTables[n].Length))
		return error;

	long_metric = *longs;
	for (n = 0; n < num_longs; n++) {
		long_metric->advance = GET_UShort();
		long_metric->bearing = GET_Short();
		long_metric++;
	}

	/* do we have an inconsistent number of metric values? */

	if (num_shorts > num_shorts_checked) {
		for (n = 0; n < num_shorts_checked; n++)
			(*shorts)[n] = GET_Short();

		/* we fill up the missing left side bearings with the    */
		/* last valid value. Since this will occur for buggy CJK */
		/* fonts usually, nothing serious will happen.           */

		for (n = num_shorts_checked; n < num_shorts; n++)
			(*shorts)[n] = (*shorts)[num_shorts_checked - 1];
	} else {
		for (n = 0; n < num_shorts; n++)
			(*shorts)[n] = GET_Short();
	}

	FORGET_Frame();

	PTRACE2(("loaded\n"));
	return TT_Err_Ok;
}

/*******************************************************************
 *
 *  Function    : Load_TrueType_Metrics_Header
 *
 *  Description : Loads either the "hhea" or "vhea" table in memory
 *
 *  Input  :  face       face table to look for
 *            vertical   a boolean.  When set, queries the optional
 *                       "vhea" table.  Otherwise, load the mandatory
 *                       "hhea" horizontal header.
 *
 *  Output :  Error code.
 *
 *  Note : This function now loads the corresponding metrics table
 *         (either hmtx or vmtx) and attaches it to the header.
 *
 ******************************************************************/

LOCAL_FUNC TT_Error Load_TrueType_Metrics_Header(PFace face, Bool vertical)
{
	DEFINE_LOCALS;

	Long i;

	TT_Horizontal_Header *header;

	PTRACE2((vertical ? "Vertical header" : "Horizontal header "));

	if (vertical) {
		face->verticalInfo = 0;

		/* The vertical header table is optional, so return quietly if */
		/* we don't find it..                                          */
		if ((i = TT_LookUp_Table(face, TTAG_vhea)) < 0)
			return TT_Err_Ok;

		face->verticalInfo = 1;
		header = (TT_Horizontal_Header *) & face->verticalHeader;
	} else {
		/* The orizontal header is mandatory, return an error if we */
		/* don't find it.                                           */
		if ((i = TT_LookUp_Table(face, TTAG_hhea)) < 0)
			return TT_Err_Horiz_Header_Missing;

		header = &face->horizontalHeader;
	}

	if (FILE_Seek(face->dirTables[i].Offset) || ACCESS_Frame(36L))
		return error;

	header->Version = GET_ULong();
	header->Ascender = GET_Short();
	header->Descender = GET_Short();
	header->Line_Gap = GET_Short();

	header->advance_Width_Max = GET_UShort();

	header->min_Left_Side_Bearing = GET_Short();
	header->min_Right_Side_Bearing = GET_Short();
	header->xMax_Extent = GET_Short();
	header->caret_Slope_Rise = GET_Short();
	header->caret_Slope_Run = GET_Short();

	header->Reserved0 = GET_Short();	/* this is caret_Offset for
										   vertical headers */
	header->Reserved1 = GET_Short();
	header->Reserved2 = GET_Short();
	header->Reserved3 = GET_Short();
	header->Reserved4 = GET_Short();

	header->metric_Data_Format = GET_Short();
	header->number_Of_HMetrics = GET_UShort();

	FORGET_Frame();

	header->long_metrics = NULL;
	header->short_metrics = NULL;

	PTRACE2(("loaded\n"));

	/* Now try to load the corresponding metrics */

	return Load_TrueType_Metrics(face, vertical);
}

/*******************************************************************
 *
 *  Function    :  Load_TrueType_Locations
 *
 *  Description :  Loads the location table into face table.
 *
 *  Input  :  face     face table to look for
 *
 *  Output :  Error code.
 *
 *  NOTE:
 *    The Font Header *must* be loaded in the leading segment
 *    calling this function.
 *
 ******************************************************************/

LOCAL_FUNC TT_Error Load_TrueType_Locations(PFace face)
{
	DEFINE_LOCALS;

	Long n, limit;
	Short LongOffsets;

	PTRACE2(("Locations "));

	LongOffsets = face->fontHeader.Index_To_Loc_Format;

	if ((n = TT_LookUp_Table(face, TTAG_loca)) < 0)
		return TT_Err_Locations_Missing;

	if (FILE_Seek(face->dirTables[n].Offset))
		return error;

	if (LongOffsets != 0) {
		face->numLocations = face->dirTables[n].Length >> 2;

		PTRACE2(("(32 bit offsets): %12lu ", face->numLocations));

		if (ALLOC_ARRAY(face->glyphLocations, face->numLocations, Long))
			return error;

		if (ACCESS_Frame(face->numLocations * 4L))
			return error;

		limit = face->numLocations;

		for (n = 0; n < limit; n++)
			face->glyphLocations[n] = GET_Long();

		FORGET_Frame();
	} else {
		face->numLocations = face->dirTables[n].Length >> 1;

		PTRACE2(("(16 bit offsets): %12lu ", face->numLocations));

		if (ALLOC_ARRAY(face->glyphLocations, face->numLocations, Long))
			return error;

		if (ACCESS_Frame(face->numLocations * 2L))
			return error;

		limit = face->numLocations;

		for (n = 0; n < limit; n++)
			face->glyphLocations[n] = (Long) ((ULong) GET_UShort() * 2);

		FORGET_Frame();
	}

	PTRACE2(("loaded\n"));

	return TT_Err_Ok;
}

/*******************************************************************
 *
 *  Function    :  Load_TrueType_Names
 *
 *  Description :  Loads the name table into face table.
 *
 *  Input  :  face     face table to look for
 *
 *  Output :  Error code.
 *
 ******************************************************************/

LOCAL_FUNC TT_Error Load_TrueType_Names(PFace face)
{
	DEFINE_LOCALS;

	UShort i, bytes;
	Long n;
	PByte storage;

	TName_Table *names;
	TNameRec *namerec;

	PTRACE2(("Names "));

	if ((n = TT_LookUp_Table(face, TTAG_name)) < 0) {
		/* The name table is required so indicate failure. */
		PTRACE2(("is missing!\n"));

		return TT_Err_Name_Table_Missing;
	}

	/* Seek to the beginning of the table and check the frame access. */
	/* The names table has a 6 byte header.                           */
	if (FILE_Seek(face->dirTables[n].Offset) || ACCESS_Frame(6L))
		return error;

	names = &face->nameTable;

	/* Load the initial names data. */
	names->format = GET_UShort();
	names->numNameRecords = GET_UShort();
	names->storageOffset = GET_UShort();

	FORGET_Frame();

	/* Allocate the array of name records. */
	if (ALLOC_ARRAY(names->names, names->numNameRecords, TNameRec) || ACCESS_Frame(names->numNameRecords * 12L)) {
		names->numNameRecords = 0;
		goto Fail;
	}

	/* Load the name records and determine how much storage is needed */
	/* to hold the strings themselves.                                */

	for (i = bytes = 0; i < names->numNameRecords; i++) {
		namerec = names->names + i;
		namerec->platformID = GET_UShort();
		namerec->encodingID = GET_UShort();
		namerec->languageID = GET_UShort();
		namerec->nameID = GET_UShort();
		namerec->stringLength = GET_UShort();
		namerec->stringOffset = GET_UShort();

#if 0
		/* check the ids */
		if (namerec->platformID <= 3) {
#endif
			/* this test takes care of 'holes' in the names tables, as */
			/* reported by Erwin                                       */
			if ((namerec->stringOffset + namerec->stringLength) > bytes)
				bytes = namerec->stringOffset + namerec->stringLength;
#if 0
		}
#endif
	}

	FORGET_Frame();

	/* Allocate storage for the strings if they exist. */

	names->storage = NULL;

	if (bytes > 0) {
		if (ALLOC(storage, bytes)
			|| FILE_Read_At(face->dirTables[n].Offset + names->storageOffset, (void *) storage, bytes))
			goto Fail_Storage;

		names->storage = storage;

		/* Go through and assign the string pointers to the name records. */

		for (i = 0; i < names->numNameRecords; i++) {
			namerec = names->names + i;
			namerec->string = storage + names->names[i].stringOffset;

			/* It is possible (but rather unlikely) that a new platform ID will be */
			/* added by Apple, so we can't rule out IDs > 3.                       */

#if 0
			if (namerec->platformID <= 3)
				namerec->string = storage + names->names[i].stringOffset;
			else {
				namerec->string = NULL;
				namerec->stringLength = 0;
			}
#endif
		}
	}
#ifdef DEBUG_LEVEL_TRACE

	for (i = 0; i < names->numNameRecords; i++) {
		int j;

		PTRACE2(("%d %d %x %d ", names->names[i].platformID, names->names[i].encodingID, names->names[i].languageID,
				 names->names[i].nameID));

		/* I know that M$ encoded strings are Unicode,            */
		/* but this works reasonable well for debugging purposes. */
		for (j = 0; j < names->names[i].stringLength; j++) {
			if (names->names[i].string) {
				Char c = *(names->names[i].string + j);

				if ((Byte) c < 128)
					PTRACE2(("%c", c));
			}
		}

		PTRACE2(("\n"));
	}

#endif							/* DEBUG_LEVEL_TRACE */

	PTRACE2(("loaded\n"));
	return TT_Err_Ok;

  Fail_Storage:
	FREE(storage);

  Fail:
	Free_TrueType_Names(face);
	return error;
}

/*******************************************************************
 *
 *  Function    :  Free_TrueType_Names
 *
 *  Description :  Frees a name table.
 *
 *  Input  :  face     face table to look for
 *
 *  Output :  TT_Err_Ok.
 *
 ******************************************************************/

LOCAL_FUNC TT_Error Free_TrueType_Names(PFace face)
{
	TName_Table *names = &face->nameTable;

	/* free strings table */
	FREE(names->names);

	/* free strings storage */
	FREE(names->storage);

	names->numNameRecords = 0;
	names->format = 0;
	names->storageOffset = 0;

	return TT_Err_Ok;
}

/*******************************************************************
 *
 *  Function    :  Load_TrueType_CVT
 *
 *  Description :  Loads cvt table into resident table.
 *
 *  Input  :  face     face table to look for
 *
 *  Output :  Error code.
 *
 ******************************************************************/

LOCAL_FUNC TT_Error Load_TrueType_CVT(PFace face)
{
	DEFINE_LOCALS;

	Long n, limit;

	PTRACE2(("CVT "));

	if ((n = TT_LookUp_Table(face, TTAG_cvt)) < 0) {
		PTRACE2(("is missing!\n"));

		face->cvtSize = 0;
		face->cvt = NULL;
		return TT_Err_Ok;
	}

	face->cvtSize = face->dirTables[n].Length / 2;

	if (ALLOC_ARRAY(face->cvt, face->cvtSize, Short))
		return error;

	if (FILE_Seek(face->dirTables[n].Offset) || ACCESS_Frame(face->cvtSize * 2L))
		return error;

	limit = face->cvtSize;

	for (n = 0; n < limit; n++)
		face->cvt[n] = GET_Short();

	FORGET_Frame();

	PTRACE2(("loaded\n"));

	return TT_Err_Ok;
}

/*******************************************************************
 *
 *  Function    :  Load_TrueType_CMap
 *
 *  Description :  Loads the cmap directory in memory.
 *                 The cmaps themselves are loaded in ttcmap.c .
 *
 *  Input  :  face
 *
 *  Output :  Error code.
 *
 ******************************************************************/

LOCAL_FUNC TT_Error Load_TrueType_CMap(PFace face)
{
	DEFINE_LOCALS;

	Long off, table_start;
	Long n, limit;

	TCMapDir cmap_dir;
	TCMapDirEntry entry_;
	PCMapTable cmap;

	PTRACE2(("CMaps "));

	if ((n = TT_LookUp_Table(face, TTAG_cmap)) < 0)
		return TT_Err_CMap_Table_Missing;

	table_start = face->dirTables[n].Offset;

	if ((FILE_Seek(table_start)) || (ACCESS_Frame(4L)))	/* 4 bytes cmap header */
		return error;

	cmap_dir.tableVersionNumber = GET_UShort();
	cmap_dir.numCMaps = GET_UShort();

	FORGET_Frame();

	off = FILE_Pos();	/* save offset to cmapdir[] which follows */

	/* save space in face table for cmap tables */
	if (ALLOC_ARRAY(face->cMaps, cmap_dir.numCMaps, TCMapTable))
		return error;

	face->numCMaps = cmap_dir.numCMaps;

	limit = face->numCMaps;
	cmap = face->cMaps;

	for (n = 0; n < limit; n++) {
		if (FILE_Seek(off) || ACCESS_Frame(8L))
			return error;

		/* extra code using entry_ for platxxx could be cleaned up later */
		cmap->loaded = FALSE;
		cmap->platformID = entry_.platformID = GET_UShort();
		cmap->platformEncodingID = entry_.platformEncodingID = GET_UShort();

		entry_.offset = GET_Long();

		FORGET_Frame();

		off = FILE_Pos();

		if (FILE_Seek(table_start + entry_.offset) || ACCESS_Frame(6L))
			return error;

		cmap->format = GET_UShort();
		cmap->length = GET_UShort();
		cmap->version = GET_UShort();

		FORGET_Frame();

		cmap->offset = FILE_Pos();

		cmap++;
	}

	PTRACE2(("loaded\n"));

	return TT_Err_Ok;
}

/*******************************************************************
 *
 *  Function    :  Load_TrueType_Programs
 *
 *  Description :  Loads the font (fpgm) and cvt programs into the
 *                 face table.
 *
 *  Input  :  face
 *
 *  Output :  Error code.
 *
 ******************************************************************/

LOCAL_FUNC TT_Error Load_TrueType_Programs(PFace face)
{
	DEFINE_LOCALS_WO_FRAME;

	Long n;

	PTRACE2(("Font program "));

	/* The font program is optional */
	if ((n = TT_LookUp_Table(face, TTAG_fpgm)) < 0) {
		face->fontProgram = NULL;
		face->fontPgmSize = 0;

		PTRACE2(("is missing!\n"));
	} else {
		face->fontPgmSize = face->dirTables[n].Length;

		if (ALLOC(face->fontProgram, face->fontPgmSize)
			|| FILE_Read_At(face->dirTables[n].Offset, (void *) face->fontProgram, face->fontPgmSize))
			return error;

		PTRACE2(("loaded, %12d bytes\n", face->fontPgmSize));
	}

	PTRACE2(("Prep program "));

	if ((n = TT_LookUp_Table(face, TTAG_prep)) < 0) {
		face->cvtProgram = NULL;
		face->cvtPgmSize = 0;

		PTRACE2(("is missing!\n"));
	} else {
		face->cvtPgmSize = face->dirTables[n].Length;

		if (ALLOC(face->cvtProgram, face->cvtPgmSize)
			|| FILE_Read_At(face->dirTables[n].Offset, (void *) face->cvtProgram, face->cvtPgmSize))
			return error;

		PTRACE2(("loaded, %12d bytes\n", face->cvtPgmSize));
	}

	return TT_Err_Ok;
}

/*******************************************************************
 *
 *  Function    :  Load_TrueType_OS2
 *
 *  Description :  Loads the OS2 Table.
 *
 *  Input  :  face
 *
 *  Output :  Error code.
 *
 ******************************************************************/

LOCAL_FUNC TT_Error Load_TrueType_OS2(PFace face)
{
	DEFINE_LOCALS;

	Long i;
	TT_OS2 *os2;

	PTRACE2(("OS/2 Table "));

	/* We now support old Mac fonts where the OS/2 table doesn't  */
	/* exist.  Simply put, we set the `version' field to 0xFFFF   */
	/* and test this value each time we need to access the table. */
	if ((i = TT_LookUp_Table(face, TTAG_OS2)) < 0) {
		PTRACE2(("is missing\n!"));
		face->os2.version = 0xFFFF;
		error = TT_Err_Ok;
		return TT_Err_Ok;
	}

	if (FILE_Seek(face->dirTables[i].Offset) || ACCESS_Frame(78L))
		return error;

	os2 = &face->os2;

	os2->version = GET_UShort();
	os2->xAvgCharWidth = GET_Short();
	os2->usWeightClass = GET_UShort();
	os2->usWidthClass = GET_UShort();
	os2->fsType = GET_Short();
	os2->ySubscriptXSize = GET_Short();
	os2->ySubscriptYSize = GET_Short();
	os2->ySubscriptXOffset = GET_Short();
	os2->ySubscriptYOffset = GET_Short();
	os2->ySuperscriptXSize = GET_Short();
	os2->ySuperscriptYSize = GET_Short();
	os2->ySuperscriptXOffset = GET_Short();
	os2->ySuperscriptYOffset = GET_Short();
	os2->yStrikeoutSize = GET_Short();
	os2->yStrikeoutPosition = GET_Short();
	os2->sFamilyClass = GET_Short();

	for (i = 0; i < 10; i++)
		os2->panose[i] = GET_Byte();

	os2->ulUnicodeRange1 = GET_ULong();
	os2->ulUnicodeRange2 = GET_ULong();
	os2->ulUnicodeRange3 = GET_ULong();
	os2->ulUnicodeRange4 = GET_ULong();

	for (i = 0; i < 4; i++)
		os2->achVendID[i] = GET_Byte();

	os2->fsSelection = GET_UShort();
	os2->usFirstCharIndex = GET_UShort();
	os2->usLastCharIndex = GET_UShort();
	os2->sTypoAscender = GET_Short();
	os2->sTypoDescender = GET_Short();
	os2->sTypoLineGap = GET_Short();
	os2->usWinAscent = GET_UShort();
	os2->usWinDescent = GET_UShort();

	FORGET_Frame();

	if (os2->version >= 0x0001) {
		/* only version 1 tables */

		if (ACCESS_Frame(8L))	/* read into frame */
			return error;

		os2->ulCodePageRange1 = GET_ULong();
		os2->ulCodePageRange2 = GET_ULong();

		FORGET_Frame();
	} else {
		os2->ulCodePageRange1 = 0;
		os2->ulCodePageRange2 = 0;
	}

	PTRACE2(("loaded\n"));

	return TT_Err_Ok;
}

/*******************************************************************
 *
 *  Function    :  Load_TrueType_PostScript
 *
 *  Description :  Loads the post table into face table.
 *
 *  Input  :  face         face table to look for
 *
 *  Output :  SUCCESS on success.  FAILURE on error.
 *
 ******************************************************************/

LOCAL_FUNC TT_Error Load_TrueType_PostScript(PFace face)
{
	DEFINE_LOCALS;

	Long i;

	TT_Postscript *post = &face->postscript;

	PTRACE2(("PostScript "));

	if ((i = TT_LookUp_Table(face, TTAG_post)) < 0)
		return TT_Err_Post_Table_Missing;

	if (FILE_Seek(face->dirTables[i].Offset) || ACCESS_Frame(32L))
		return error;

	/* read frame data into face table */

	post->FormatType = GET_ULong();
	post->italicAngle = GET_ULong();
	post->underlinePosition = GET_Short();
	post->underlineThickness = GET_Short();
	post->isFixedPitch = GET_ULong();
	post->minMemType42 = GET_ULong();
	post->maxMemType42 = GET_ULong();
	post->minMemType1 = GET_ULong();
	post->maxMemType1 = GET_ULong();

	FORGET_Frame();

	/* we don't load the glyph names, we do that in a */
	/* library extension (ftxpost).                   */

	PTRACE2(("loaded\n"));

	return TT_Err_Ok;
}

/*******************************************************************
 *
 *  Function    :  Load_TrueType_Hdmx
 *
 *  Description :  Loads the horizontal device metrics table.
 *
 *  Input  :  face         face object to look for
 *
 *  Output :  SUCCESS on success.  FAILURE on error.
 *
 ******************************************************************/

LOCAL_FUNC TT_Error Load_TrueType_Hdmx(PFace face)
{
	DEFINE_LOCALS;

	TT_Hdmx_Record *rec;
	TT_Hdmx hdmx;
	Long table;
	UShort n, num_glyphs;
	Long record_size;

	hdmx.version = 0;
	hdmx.num_records = 0;
	hdmx.records = 0;

	face->hdmx = hdmx;

	if ((table = TT_LookUp_Table(face, TTAG_hdmx)) < 0)
		return TT_Err_Ok;

	if (FILE_Seek(face->dirTables[table].Offset) || ACCESS_Frame(8L))
		return error;

	hdmx.version = GET_UShort();
	hdmx.num_records = GET_Short();
	record_size = GET_Long();

	FORGET_Frame();

	/* Only recognize format 0 */

	if (hdmx.version != 0)
		return TT_Err_Ok;

	if (ALLOC(hdmx.records, sizeof(TT_Hdmx_Record) * hdmx.num_records))
		return error;

	num_glyphs = face->numGlyphs;
	record_size -= num_glyphs + 2;
	rec = hdmx.records;

	for (n = 0; n < hdmx.num_records; n++) {
		/* read record */

		if (ACCESS_Frame(2L))
			goto Fail;

		rec->ppem = GET_Byte();
		rec->max_width = GET_Byte();

		FORGET_Frame();

		if (ALLOC(rec->widths, num_glyphs) || FILE_Read(rec->widths, num_glyphs))
			goto Fail;

		/* skip padding bytes */
		if (record_size > 0)
			if (FILE_Skip(record_size))
				goto Fail;

		rec++;
	}

	face->hdmx = hdmx;

	return TT_Err_Ok;

  Fail:
	for (n = 0; n < hdmx.num_records; n++)
		FREE(hdmx.records[n].widths);

	FREE(hdmx.records);
	return error;
}

/*******************************************************************
 *
 *  Function    :  Free_TrueType_Hdmx
 *
 *  Description :  Frees the horizontal device metrics table.
 *
 *  Input  :  face         face object to look for
 *
 *  Output :  TT_Err_Ok.
 *
 ******************************************************************/

LOCAL_FUNC TT_Error Free_TrueType_Hdmx(PFace face)
{
	UShort n;

	if (!face)
		return TT_Err_Ok;

	for (n = 0; n < face->hdmx.num_records; n++)
		FREE(face->hdmx.records[n].widths);

	FREE(face->hdmx.records);
	face->hdmx.num_records = 0;

	return TT_Err_Ok;
}

/*******************************************************************
 *
 *  Function    :  Load_TrueType_Any
 *
 *  Description :  Loads any font table into client memory. Used by
 *                 the TT_Get_Font_Data() API function.
 *
 *  Input  :  face     face object to look for
 *
 *            tag      tag of table to load. Use the value 0 if you
 *                     want to access the whole font file, else set
 *                     this parameter to a valid TrueType table tag
 *                     that you can forge with the MAKE_TT_TAG
 *                     macro.
 *
 *            offset   starting offset in the table (or the file
 *                     if tag == 0 )
 *
 *            buffer   address of target buffer
 *
 *            length   address of decision variable :
 *
 *                       if length == NULL :
 *                             load the whole table. returns an
 *                             an error if 'offset' == 0 !!
 *
 *                       if *length == 0 :
 *                             exit immediately, returning the
 *                             length of the given table, or of
 *                             the font file, depending on the
 *                             value of 'tag'
 *
 *                       if *length != 0 :
 *                             load the next 'length' bytes of
 *                             table or font, starting at offset
 *                             'offset' (in table or font too).
 *
 *  Output :  Error condition
 *
 ******************************************************************/

LOCAL_FUNC TT_Error Load_TrueType_Any(PFace face, ULong tag, Long offset, void *buffer, Long * length)
{
	TT_Stream stream;
	TT_Error error;
	Long table;
	ULong size;

	if (tag != 0) {
		/* look for tag in font directory */
		table = TT_LookUp_Table(face, tag);
		if (table < 0)
			return TT_Err_Table_Missing;

		offset += face->dirTables[table].Offset;
		size = face->dirTables[table].Length;
	} else
		/* tag = 0 -- the use want to access the font file directly */
		size = TT_Stream_Size(face->stream);

	if (length && *length == 0) {
		*length = size;
		return TT_Err_Ok;
	}

	if (length)
		size = *length;

	if (!USE_Stream(face->stream, stream))
		(void) FILE_Read_At(offset, buffer, size);
	DONE_Stream(stream);

	return error;
}

/* END */
